package com.gitee.wsl.ext.reflect

import kotlin.compareTo
import kotlin.reflect.KClass
import kotlin.reflect.KClassifier
import kotlin.reflect.KType
import kotlin.reflect.KTypeParameter
import kotlin.reflect.KTypeProjection
import kotlin.reflect.full.createType
import kotlin.reflect.full.withNullability as baseNullability

actual fun KType.withNullability(nullable: Boolean): KType {
    return baseNullability(nullable)
}

actual val KClass<*>.qualifiedNameForComparison: String?
    get() {
        // Unfortunately qualifiedName isn't implemented in JS
        return simpleName
    }

 actual class KTypeImpl
actual constructor(
    actual override val classifier: KClassifier?,
    actual override val arguments: List<KTypeProjection>,
    actual override val isMarkedNullable: Boolean,
    actual override val annotations: List<Annotation>,
) : KType, EitherNetKType {
    private val impl = EitherNetKTypeImpl(classifier, arguments, isMarkedNullable, annotations)

    override fun equals(other: Any?) = impl == other

    override fun hashCode() = impl.hashCode()

    override fun toString() = impl.toString()
}

private fun KType.resolveTypeProjection(referencedType: KTypeProjection): KTypeProjection =
    when (val referencedClassifier = referencedType.type?.classifier) {
        is KTypeParameter -> referencedType.copy(type = resolveTypeParam(referencedClassifier))
        else              -> referencedType
    }

fun KType.resolveTypeParam(param: KTypeParameter): KType {
    val index = indexOfTypeParamNamed(param.name)
    return when {
        index < 0 -> param.upperBounds[0] // kind of a hack, wont work with complex generics
        else      -> arguments[index].type!!
    }
}

/**
 * Resolve a type that is referenced in the @receiver
 */
internal fun KType.resolveInnerType(referencedType: KType): KType =
    when (val referencedClassifier = referencedType.classifier) {
        is KTypeParameter -> resolveTypeParam(referencedClassifier)
        is KClass<*>      -> referencedClassifier.createType(
            arguments = referencedType.arguments.map { resolveTypeProjection(it) },
            nullable = referencedType.isMarkedNullable
        )

        else              -> throw TypeTokenResolutionError(referencedType = referencedType, context = this)
    }
private fun KType.indexOfTypeParamNamed(name: String): Int =
    (classifier as KClass<*>).typeParameters.indexOfFirst { it.name == name }
